package com.caseProject.puremvc.mediator
{
	import com.caseProject.puremvc.mediator.events.GraphicEvent;
	import com.caseProject.puremvc.mediator.gui.Element;
	import com.caseProject.puremvc.mediator.gui.ElementAssociation;
	import com.caseProject.puremvc.mediator.gui.EndStateElement;
	import com.caseProject.puremvc.mediator.gui.StartStateElement;
	import com.caseProject.puremvc.mediator.gui.StateElement;
	import com.caseProject.puremvc.mediator.gui.Transition;
	import com.caseProject.puremvc.mediator.states.AbstractState;
	import com.caseProject.puremvc.mediator.states.commonMenuStates.IdleConcreteState;
	import com.caseProject.puremvc.mediator.states.commonMenuStates.NoteConcreteState;
	import com.caseProject.puremvc.mediator.states.commonMenuStates.TransitionConcreteState;
	import com.caseProject.puremvc.mediator.states.elementMenuStates.EndConcreteState;
	import com.caseProject.puremvc.mediator.states.elementMenuStates.StartConcreteState;
	import com.caseProject.puremvc.mediator.states.elementMenuStates.StateConcreteState;
	import com.caseProject.puremvc.mediator.view.StageView;
	import com.caseProject.puremvc.model.Diagram;
	import com.caseProject.puremvc.model.DiagramNames;
	import com.caseProject.puremvc.model.GraphicTypeNames;
	import com.caseProject.puremvc.model.ProjectProxy;
	import com.caseProject.puremvc.model.vo.DiagramActivityVO;
	import com.caseProject.puremvc.model.vo.DiagramStateVO;
	import com.caseProject.puremvc.model.vo.ElementVO;
	import com.caseProject.puremvc.model.vo.TransitionVO;
	import com.caseProject.puremvc.notifications.NotificationNames;
	
	import flash.events.MouseEvent;
	import flash.geom.Point;
	
	import org.puremvc.as3.interfaces.IMediator;
	import org.puremvc.as3.interfaces.INotification;
	import org.puremvc.as3.patterns.mediator.Mediator;
	
	public class StageMediator extends Mediator implements IMediator
	{
		public static const NAME:String = "StageMediator";
		private var currentMenuState:AbstractState;
		private var clicado:Boolean = false;
		private var elementsAssocList:Vector.<ElementAssociation>;
		
		public function StageMediator(viewComponent:StageView)
		{
			super(NAME, viewComponent);
			
			this.stageView.addEventListener(GraphicEvent.DELETE_ELEMENT, onDeleteElementGraphic);
			this.stageView.addEventListener(GraphicEvent.DELETE_PARTITION, onDeletePartitionGraphic);
			this.stageView.addEventListener(GraphicEvent.DELETE_TRANSITION, onDeleteTransitionGraphic);
			
			this.elementsAssocList = new Vector.<ElementAssociation>();
			
			this.changeState(IdleConcreteState);
			
			this.stageView.addEventListener(MouseEvent.CLICK, onClickStageView);
			
			this.stageView.addEventListener(GraphicEvent.CLICK, onElementSelect);
			this.stageView.addEventListener(GraphicEvent.EDIT, onEdit);
			this.stageView.addEventListener(GraphicEvent.IDLE, onIdle);
		}
		
		protected function onIdle(event:GraphicEvent):void
		{
			this.sendNotification(NotificationNames.GRAPHIC_EDIT_STATE);
		}
		
		public function get stageView():StageView
		{
			return viewComponent as StageView;
		}
		
		override public function listNotificationInterests():Array
		{
			return [
				NotificationNames.STATE_MENU_END,
				NotificationNames.STATE_MENU_NOTE,
				NotificationNames.STATE_MENU_START,
				NotificationNames.STATE_MENU_STATE,
				NotificationNames.STATE_MENU_TRANSITION,
				NotificationNames.STATE_MENU_IDLE,
				NotificationNames.MENU_LIST_OPEN_DIAGRAM,
				NotificationNames.RETRIEVE_LINKED_TRANSITIONS_SUCCESS,
				NotificationNames.OPEN_PROJECT_SUCESS,
				NotificationNames.CREATE_PROJECT_SUCESS,
				NotificationNames.CLOSE_PROJECT_SUCESS
			];
		}
		
		override public function handleNotification(notification:INotification):void
		{
			switch(notification.getName())
			{
				case NotificationNames.CLOSE_PROJECT_SUCESS:
				{
					this.stageView.clearStage();
					break;
				}
					
				case NotificationNames.CREATE_PROJECT_SUCESS:
				{
					this.stageView.clearStage();
					break;
				}
					
				case NotificationNames.OPEN_PROJECT_SUCESS:
				{
					this.stageView.clearStage();
					break;
				}
					
				case NotificationNames.MENU_LIST_OPEN_DIAGRAM:
				{
					var diagramVO:Diagram = notification.getBody() as Diagram;
					this.drawObjects(diagramVO);
					break;
				}
					
				case NotificationNames.STATE_MENU_END:
				{
					this.changeState(EndConcreteState);
					break;
				}
					
				case NotificationNames.STATE_MENU_NOTE:
				{
					this.changeState(NoteConcreteState);
					break;
				}
					
				case NotificationNames.STATE_MENU_START:
				{
					this.changeState(StartConcreteState);
					break;
				}
					
				case NotificationNames.STATE_MENU_STATE:
				{
					this.changeState(StateConcreteState);
					break;
				}
					
				case NotificationNames.STATE_MENU_IDLE:
				{
					this.changeState(IdleConcreteState);
					this.sendNotification(NotificationNames.GRAPHIC_EDIT_STATE);
					break;
				}
					
				case NotificationNames.STATE_MENU_TRANSITION:
				{
					this.changeState(TransitionConcreteState);				
					break;
				}
					
				case NotificationNames.RETRIEVE_LINKED_TRANSITIONS_SUCCESS:
				{
					var list:Vector.<TransitionVO> = notification.getBody() as Vector.<TransitionVO>;
					for (var i:int = 0; i < list.length; i++) 
					{
						this.stageView.removeGraphics(list[i]);
						this.sendNotification(NotificationNames.GRAPHIC_DELETE_TRANSITION, list[i]);
					}
					break;
				}
					
				default:
				{
					break;
				}
			}
		}
		
		protected function onElementSelect(event:GraphicEvent):void
		{
			var target:Element = Element(event.target) as Element; 

			var posX:Number = target.vo.posX;
			var posY:Number = target.vo.posY;
			
			if (this.currentMenuState.type != GraphicTypeNames.IDLE_TYPE)
			{
				// se clicado for StartStateElement e já existir na lista de associações 
				if (target is StartStateElement && this.exists(target, this.elementsAssocList) != -1)
				{} else {
					//se for o primeiro clicado
					if (!this.clicado)
					{
						//se não for EndStateElement
						if (!(target is EndStateElement))
						{
							var transition:Transition = this.currentMenuState.draw() as Transition;
							transition.mouseEnabled = false;
							
							transition.fromPoint = new Point(posX, posY);
							transition.toPoint = new Point(posX, posY);
							
							var transitionVO:TransitionVO = new TransitionVO();
							transitionVO.elementFromId = target.vo.id;
							
							transition.vo = transitionVO;
							transition.id = transitionVO.id;
							
							this.addElementAssociation(target, transition);
							
							this.stageView.addElement(transition);
							this.stageView.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
							
							transition.onAddedToStage();
							this.clicado = true;
						}
					} else {
						this.stageView.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
						
						var lastTransition:Transition = this.stageView.getElementAt(this.stageView.numElements-1) as Transition;
						lastTransition.toPoint = new Point(posX, posY);
						
						TransitionVO(lastTransition.vo).elementToId = target.vo.id;
						
						this.addElementAssociation(target, lastTransition);
						lastTransition.onAddedToStage();
						
						this.clicado = false;
						
						var proxy:ProjectProxy = this.facade.retrieveProxy(ProjectProxy.NAME) as ProjectProxy;
						DiagramStateVO(proxy.currentDiagram).addTransition(TransitionVO(lastTransition.vo));
						DiagramStateVO(proxy.currentDiagram).elemAssoc = this.elementsAssocList;
					}
				}
			}
			
			if (this.currentMenuState.type == GraphicTypeNames.IDLE_TYPE)
			{
				target.enableDrag = true;
				target.addEventListener(MouseEvent.MOUSE_MOVE, onElementMove);
			}
		}
		
		protected function onElementMove(event:MouseEvent):void
		{
			event.stopPropagation();
			
			var element:Element = Element(event.currentTarget) as Element;
			var elementVO:ElementVO = element.vo as ElementVO;
			
			var index:int = -1;
			for (var i:int = 0; i < this.elementsAssocList.length; i++) 
			{
				if (this.elementsAssocList[i].element == element)
				{
					index = i;
					break;					
				}
			}
			
			if (index != -1)
			{
				var transitions:Vector.<Transition> = this.elementsAssocList[index].transitions;
				
				for (var j:int = 0; j < transitions.length; j++) 
				{
					if (TransitionVO(transitions[j].vo).elementFromId == element.id)
					{
						transitions[j].fromPoint = new Point(elementVO.posX, elementVO.posY);
					}
					else if (TransitionVO(transitions[j].vo).elementToId == element.id)
					{
						transitions[j].toPoint = new Point(elementVO.posX, elementVO.posY);
					}
				}
			}
		}
		
		protected function onMouseMove(event:MouseEvent):void
		{
			var lastTransition:Transition = this.stageView.getElementAt(this.stageView.numElements-1) as Transition;
			lastTransition.toPoint = new Point(this.stageView.mouseX, this.stageView.mouseY);			
		}
		
		private function addElementAssociation(element:Element, transition:Transition):void
		{
			var index:int = this.exists(element, this.elementsAssocList);
			
			if (index == -1)
			{
				var elementAssoc:ElementAssociation = new ElementAssociation(element, transition);
				this.elementsAssocList.push(elementAssoc);
			} else {
				this.elementsAssocList[index].addTransition(transition);
			}
		}
		
		private function exists(element:Element, elementsAssocList:Vector.<ElementAssociation>):int
		{
			var exists:Boolean = false;
			var index:int = -1;
			for (var i:int = 0; i < this.elementsAssocList.length && !exists; i++) 
			{
				if (this.elementsAssocList[i].element == element)
				{
					index = i;
					exists = true;
				}	
			}
			
			return index;
		}
		
		private function existsStartState():int
		{
			var exists:Boolean = false;
			var index:int = -1;
			
			for (var i:int = 0; i < this.stageView.numElements && !exists; i++) 
			{
				if (this.stageView.getElementAt(i) is StartStateElement)
				{
					index = i;
					exists = true;
				}	
			}
			
			return index;
		}
		
		protected function onClickStageView(event:MouseEvent):void
		{
			if (this.currentMenuState.type != GraphicTypeNames.TRANSITION_TYPE && this.currentMenuState.type != GraphicTypeNames.IDLE_TYPE)
			{
				if (!(event.target is Element))
				{
					//se o clicado for StartStateElement e já existir um no stage
					if ((this.currentMenuState.type == GraphicTypeNames.START_TYPE) && this.existsStartState() != -1)
					{
						
					} else {
						var element:Element = this.currentMenuState.draw() as Element;
						element.x = this.stageView.mouseX;
						element.y = this.stageView.mouseY;
						
						var elementVO:ElementVO = new ElementVO();
						elementVO.type = this.currentMenuState.type;
						elementVO.posX = this.stageView.mouseX;
						elementVO.posY = this.stageView.mouseY;
						elementVO.name = "State";
						
						elementVO.widthValue = 100;
						elementVO.heightValue = 50;
						
						element.id = elementVO.id;
						element.vo = elementVO;
						
						this.stageView.addElement(element);
						
						var proxy:ProjectProxy = this.facade.retrieveProxy(ProjectProxy.NAME) as ProjectProxy;
						DiagramStateVO(proxy.currentDiagram).addElement(elementVO);
					}
				}
			}
		}
		
		private function drawObjects(diagramVO:Diagram):void
		{
			var type:String = diagramVO.type;
			var elementVOList:Vector.<ElementVO>;
			
			if (type == DiagramNames.ACTIVITY)
			{
				elementVOList = DiagramActivityVO(diagramVO).elements;
			}
			else if (type == DiagramNames.STATE)
			{
				elementVOList = DiagramStateVO(diagramVO).elements;			
			}
			
			for (var i:int = 0; i < elementVOList.length; i++)
			{
				var tempMenuState:AbstractState = this.retrieveMenuState(elementVOList[i].type);
				var element:Element = tempMenuState.draw() as Element;
				
				element.x = elementVOList[i].posX;
				element.y = elementVOList[i].posY;
				
				element.rename(elementVOList[i]);
				
				element.id = elementVOList[i].id;
				element.vo = elementVOList[i];
				
				element.redraw(ElementVO(element.vo).widthValue, ElementVO(element.vo).heightValue);
				
				this.stageView.addElement(element);
			}
			
			this.drawLinkedTransitions(diagramVO);
		}
		
		private function drawLinkedTransitions(diagramVO:Diagram):void
		{
			var type:String = diagramVO.type;
			var transitionVOList:Vector.<TransitionVO>;
			
			if (type == DiagramNames.ACTIVITY)
			{
				transitionVOList = DiagramActivityVO(diagramVO).transitions;
			}
			else if (type == DiagramNames.STATE)
			{
				transitionVOList = DiagramStateVO(diagramVO).transitions;
			}
			
			for (var i:int = 0; i < transitionVOList.length; i++)
			{
				var tempMenuState:AbstractState = this.retrieveMenuState(GraphicTypeNames.TRANSITION_TYPE);
				var transition:Transition = tempMenuState.draw() as Transition;
				
				transition.id = transitionVOList[i].id;
				transition.vo = transitionVOList[i];
				
				var elemToId:int = TransitionVO(transition.vo).elementToId;
				var elemFromId:int = TransitionVO(transition.vo).elementFromId;
				
				var elementTo:Element = this.getElementById(elemToId);
				var elementFrom:Element = this.getElementById(elemFromId);
				
				var elementToVO:ElementVO = elementTo.vo as ElementVO; 
				var posXTo:int = elementToVO.posX;
				var posYTo:int = elementToVO.posY;
				
				var elementFromVO:ElementVO = elementFrom.vo as ElementVO; 
				var posXFrom:int = elementFromVO.posX;
				var posYFrom:int = elementFromVO.posY;
				
				this.addElementAssociation(elementFrom, transition);
				this.addElementAssociation(elementTo, transition);
				
				transition.fromPoint = new Point(posXFrom, posYFrom);
				transition.toPoint = new Point(posXTo, posYTo);
				
				this.stageView.addElement(transition);
			}
		}
		
		private function getElementById(id:int):Element
		{
			var element:Element;
			
			for (var i:int = 0; i < this.stageView.numElements; i++) 
			{
				element = this.stageView.getElementAt(i) as Element; 
				if (element.id == id)
				{
					return element;
				}
			}			
			return null;
		}		
		
		
		private function retrieveMenuState (type:String):AbstractState 
		{
			var tempMenuState:AbstractState;
			
			switch (type)
			{
				case GraphicTypeNames.END_TYPE:
				{
					tempMenuState = new EndConcreteState () as AbstractState;
					break;
				}
				
				case GraphicTypeNames.NOTE_TYPE:
				{
					tempMenuState = new NoteConcreteState () as AbstractState;
					break;
				} 
				
				case GraphicTypeNames.START_TYPE:
				{
					tempMenuState = new StartConcreteState () as AbstractState;
					break;
				}
					
				case GraphicTypeNames.STATE_TYPE:
				{
					tempMenuState = new StateConcreteState () as AbstractState;
					break;
				}
					
				case GraphicTypeNames.TRANSITION_TYPE:
				{
					tempMenuState = new TransitionConcreteState () as AbstractState;
					break;
				}
			}			
			return tempMenuState;			
		}
		
		private function changeState (NewStateConcrete:Class):void 
		{
			this.currentMenuState = new NewStateConcrete () as AbstractState;
		}
		
		protected function onEdit(event:GraphicEvent):void
		{
			if (event.target is StateElement)
			{
				this.sendNotification(NotificationNames.MENU_EDIT_STATE, event.target);
			} 
			else if (event.target is Transition)
			{
				this.sendNotification(NotificationNames.MENU_EDIT_TRANSITION, event.target);
			}			
		}
		
		protected function onDeleteTransitionGraphic(event:GraphicEvent):void
		{
			event.stopPropagation();
			this.sendNotification(NotificationNames.GRAPHIC_DELETE_TRANSITION, event.vo);			
		}
		
		protected function onDeleteElementGraphic(event:GraphicEvent):void
		{
			event.stopPropagation();
			this.sendNotification(NotificationNames.RETRIEVE_LINKED_TRANSITIONS, event.vo);
			this.sendNotification(NotificationNames.GRAPHIC_DELETE_ELEMENT, event.vo);			
		}
		
		protected function onDeletePartitionGraphic(event:GraphicEvent):void
		{
			event.stopPropagation();
			this.sendNotification(NotificationNames.GRAPHIC_DELETE_PARTITION, event.vo);
			//TODO: remover elementos associados, remover transições associadas, sendNot(DELETE_ELEMENTS)
		}
	}
}